package com.insigma.business.gateway.api;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.serializer.SerializerFeature;

import com.insigma.business.gateway.dto.*;
import com.insigma.business.common.utils.SpringContextUtils;
import com.insigma.business.qrcode.common.annotation.ApiServiceType;
import com.insigma.business.qrcode.common.template.BasePagingResponse;
import com.insigma.business.qrcode.common.util.ExceptionUtil;
import com.insigma.business.qrcode.common.util.LogUtil;
import com.insigma.business.qrcode.common.vo.BaseResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;


@Service("serviceInvoker")
public class ServiceInvoker {

    private static final Logger logger = LoggerFactory.getLogger(ServiceInvoker.class);

    
    private Map<String, Map<String, MethodInfo>> services = new HashMap<>();

    
    private final List<ServiceInterceptor> interceptorList = new ArrayList<>();

    
    public synchronized void registerInterceptor(final ServiceInterceptor interceptor) {

        LogUtil.info(logger, "注册拦截器：", interceptor.getClass().getCanonicalName());

        this.interceptorList.add(interceptor);

        
        OrderComparator.sort(interceptorList);
    }


    public synchronized void registerService(final Object target, final Class<?> interfaceClazz) {
        LogUtil.info(logger, "开始注册target:", target.getClass().getName(), ",interfaceClazz:",
                interfaceClazz);

        Map<String, MethodInfo> methodName2MethodInfoMap = new HashMap<>();

        for (Method method : interfaceClazz.getMethods()) {

            if (null == AnnotationUtils.getAnnotation(method, ApiServiceType.class)) {
                continue;
            }

            Type[] types = method.getGenericParameterTypes();
            MethodInfo methodInfo = new MethodInfo();
            methodInfo.setMethod(method);
            methodInfo.setParamCount(types.length);
            methodInfo.setTarget(target);
            methodInfo.setParamType(types);

            LogUtil.info(logger, "开始解析方法:", method.getName());

            MethodInfo oldMethodInfo = methodName2MethodInfoMap.put(method.getName(), methodInfo);
            if (null != oldMethodInfo) {
                ExceptionUtil.error(logger, "方法不能重载", method.getName());
                throw new IllegalArgumentException(
                        "不支持方法重载：" + interfaceClazz.getName() + "." + method.getName());
            }
        }

        this.services.put(interfaceClazz.getName(), methodName2MethodInfoMap);

        LogUtil.info(logger, "注册成功target:", target.getClass().getName(), ",interfaceClazz:",
                interfaceClazz);
    }

    public synchronized void registerService(Map<Method, MethodClassDTO> methodMap) {
        Map<String, MethodInfo> methodName2MethodInfoMap = new HashMap<>();
        for (Entry<Method, MethodClassDTO> methodStringEntry : methodMap.entrySet()) {
            Method method = methodStringEntry.getKey();
            LogUtil.info(logger, "开始注册target:", methodStringEntry.getValue(), ",interfaceClazz:",
                    method.getClass().getName());
            if (null == AnnotationUtils.getAnnotation(method, ApiServiceType.class)) {
                continue;
            }
            Type[] types = method.getGenericParameterTypes();
            MethodInfo methodInfo = new MethodInfo();
            methodInfo.setMethod(method);
            methodInfo.setParamCount(types.length);
            methodInfo.setTarget(methodStringEntry.getValue());
            methodInfo.setParamType(types);

            LogUtil.info(logger, "开始解析方法:", method.getName());

            methodName2MethodInfoMap.put(method.getName(), methodInfo);
            this.services.put(methodStringEntry.getValue().getClassBeanName(), methodName2MethodInfoMap);

            LogUtil.info(logger, "注册成功target:", methodStringEntry.getValue(), ",interfaceClazz:",
                    method.getClass().getName());
        }
    }

    public List<ServiceInfo> listServices() {
        List<ServiceInfo> result = new ArrayList<>();
        for (Entry<String, Map<String, MethodInfo>> entry : this.services.entrySet()) {
            String interfaceType = entry.getKey();

            for (Entry<String, MethodInfo> entry2 : entry.getValue().entrySet()) {
                String methodName = entry2.getKey();
                MethodInfo methodInfo = entry2.getValue();

                ServiceInfo serviceInfo = new ServiceInfo();

                serviceInfo.setInterfaceType(interfaceType);

                serviceInfo.setMethodName(methodName);

                Annotation[] ans = methodInfo.getMethod().getAnnotations();
                Map<String, String> anMap = new HashMap<String, String>();
                for (Annotation an : ans) {
                    if (an instanceof ApiServiceType) {
                        anMap.put(SpiConstant.KEY_API_TYPE, ((ApiServiceType) an).value());
                        anMap.put(SpiConstant.KEY_API_DESC, ((ApiServiceType) an).desc());
                        anMap.put(SpiConstant.KEY_API_NAME, ((ApiServiceType) an).name());
                    }
                }
                serviceInfo.setAnnotations(anMap);

                Class<?>[] clazzArray = methodInfo.getMethod().getParameterTypes();
                List<String> classCanonicalNameList = new ArrayList<>(clazzArray.length);
                for (Class<?> clazz : clazzArray) {
                    classCanonicalNameList.add(clazz.getCanonicalName());
                }
                serviceInfo.setParamType(classCanonicalNameList);

                

                result.add(serviceInfo);
            }
        }

        return result;
    }

    
    public BaseResponse invoke(final ApiServiceContext context, String requestData)
            throws InvocationTargetException, IllegalAccessException {
        
        final Map<String, MethodInfo> methodName2MethodInfo = this.services.get(context.getClassName());
        SpiCheckUtils.checkIsTrue(methodName2MethodInfo != null, "未找到服务：", context.getClassName());

        
        final MethodInfo methodInfo = methodName2MethodInfo.get(context.getMethodName());
        SpiCheckUtils.checkIsTrue(methodInfo != null, "未找到方法：", context.getMethodName());

        if(methodInfo.getParamCount()==0){
            requestData = "";
        }

        
        List<Object> params = JSON.parseArray("[" + requestData + "]", methodInfo.getParamType());
        SpiCheckUtils.checkIsTrue(params.size() == methodInfo.getParamCount(),
                "请求参数个数错误：" + methodInfo.getParamCount());

        
        return invokeInternal(methodInfo, params);
    }

    
    private BaseResponse invokeInternal(final MethodInfo methodInfo, final List<Object> params)
            throws IllegalAccessException, InvocationTargetException {
        Method method = methodInfo.getMethod();
        ReflectionUtils.makeAccessible(method);
        Object[] args = params.toArray();
        Object target = methodInfo.getTarget();
        Object resultValue = null;

        
        for (ServiceInterceptor serviceInterceptor : interceptorList) {
            BaseResponse ret = serviceInterceptor.beforeInvoke(method, args, target);
            if (ret != null) {
                resultValue = ret;
                break;
            }
        }

        
        if (resultValue == null) {
            //目前beanName匹配存在首字母小写、首字母小写+末尾Impl、首字母小写+末尾Service替换Provider
            try {
                Object bean = null;
                MethodClassDTO target1 = (MethodClassDTO) methodInfo.getTarget();
                String beanName = target1.getClassBeanName();
                try {
                    //首字母小写
                    bean = SpringContextUtils.getBean(beanName);
                } catch (NoSuchBeanDefinitionException e1) {
                    try {
                        //首字母小写+末尾Impl
                        bean = SpringContextUtils.getBean(beanName + "Impl");
                    } catch (NoSuchBeanDefinitionException e2) {
                        //首字母小写+末尾Service替换Provider
                        String replaceBeanName = beanName.replace("Service", "Provider");
                        bean = SpringContextUtils.getBean(replaceBeanName);
                    }
                }
                resultValue = method.invoke(bean, args);
            } catch (InvocationTargetException e) {

                Throwable t = e.getCause();
                for (ServiceInterceptor serviceInterceptor : interceptorList) {
                    serviceInterceptor.throwsInvoke(t, method, args, target);
                }
                throw e;
            }
        }
        for (ServiceInterceptor serviceInterceptor : interceptorList) {
            serviceInterceptor.afterInvoke(resultValue, method, args, target);
        }

        final PropertyFilter propertyFilter = new PropertyFilter() {
            @Override
            public boolean apply(Object object, String name, Object value) {
                if ("success".equalsIgnoreCase(name)) {
                    return false;
                }
                return true;
            }
        };

//        final String resultJsonString = JSON.toJSONString(resultValue);
        final String resultJsonString = JSON
                .toJSONString(resultValue, SerializerFeature.WriteMapNullValue,
                        SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.MapSortField);

        
        for (ServiceInterceptor serviceInterceptor : interceptorList) {
            BaseResponse ret = serviceInterceptor
                    .afterInvoke(resultJsonString, method, args, target);
            if (ret != null) {
                resultValue = ret;
                break;
            }
        }

        SpiCheckUtils.checkIsTrue(resultValue instanceof BaseResponse, "远端服务不符合规范，请联系对方订正");

        if (resultValue instanceof BasePagingResponse) {
            return buildPagingResponse((BasePagingResponse) resultValue);
        } else {
            return buildResponse((BaseResponse) resultValue);
        }
    }

    
    @SuppressWarnings("unchecked")
    private BaseResponse buildResponse(final BaseResponse remoteResponse) {
        BaseResponse baseResponse = new BaseResponse<>();
        baseResponse.setSuccess(remoteResponse.isSuccess());
        baseResponse.setErrorCtx(remoteResponse.getErrorCtx());
        Object obj = JSON.parse(JSON.toJSONString(remoteResponse.getDto(), SerializerFeature.MapSortField), Feature.OrderedField);
        if (obj instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject) obj;
            obj = jsonObject.toJavaObject(remoteResponse.getDto().getClass());
        }
        baseResponse.setDto(obj);

        return baseResponse;
    }

    
    @SuppressWarnings("unchecked")
    private BasePagingResponse buildPagingResponse(final BasePagingResponse remoteResponse) {
        BasePagingResponse baseResponse = new BasePagingResponse<>();
        baseResponse.setSuccess(remoteResponse.isSuccess());
        baseResponse.setErrorCtx(remoteResponse.getErrorCtx());
        baseResponse.setPageNo(remoteResponse.getPageNo());
        baseResponse.setPageSize(remoteResponse.getPageSize());
        baseResponse.setTotalCount(remoteResponse.getTotalCount());
        baseResponse.setAllCount(remoteResponse.getAllCount());
        baseResponse.setHead(remoteResponse.getHead());
        baseResponse.setExtra(remoteResponse.getExtra());
        baseResponse.setDto(JSON.parse(JSON.toJSONString(remoteResponse.getDto(), SerializerFeature.MapSortField), Feature.OrderedField));

        return baseResponse;
    }

}
